using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using System.Collections.ObjectModel;
using Tomahawk.Runtime.Rendering.Objects;
using Tomahawk.Runtime.Rendering.Interfaces;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Tomahawk.Runtime.Rendering;

namespace Tomahawk.Runtime.Scene
{
    /// <summary>
    /// This class an object that can be placed in the scene.
    /// </summary>
    public class SceneNode
    {
        protected SceneNode parentSceneNode = null;

        protected Collection<SceneNode> childSceneNodes;

        protected Entity attachedEntity = null;

        private bool visible = true;

        #region "Public Properties"

        public bool Visible
        {
            get { return visible; }
            set { visible = value; }
        }

        public Vector3 Position = Vector3.Zero;

        public Vector3 Scale = Vector3.One;

        public Quaternion Rotation = Quaternion.Identity;

        public Matrix WorldMatrix = Matrix.Identity;

        public Matrix LocalMatrix = Matrix.Identity;

        /// <summary>
        /// Allow access to the attached entity
        /// </summary>
        public Entity AttachedEntity
        {
            get { return this.attachedEntity; }
        }

        /// <summary>
        /// Allow access to the parent scene node
        /// </summary>
        public SceneNode Parent
        {
            get { return this.parentSceneNode; }
        }

        #endregion

        #region "Root Scene Node Utility"

        /// <summary>
        /// Flag indicating the root scene node was already created
        /// </summary>
        static bool rootSceneNodeCreated = false;

        /// <summary>
        /// Create the root scene node instance 
        /// </summary>
        internal static SceneNode CreateRootSceneNode()
        {
            if (!rootSceneNodeCreated)
            {
                rootSceneNodeCreated = true;
                return new SceneNode();
            }
            else
                throw new Exception("Root Scene Node already created!");
        }

        #endregion

        /// <summary>
        /// Protected Constructor, avoid freely creation of scenenodes
        /// </summary>
        protected SceneNode()
        {
            //create the child collection
            this.childSceneNodes = new Collection<SceneNode>();
        }

        #region "Child Scene Nodes"

        /// <summary>
        /// Create a child scene node
        /// </summary>
        /// <returns></returns>
        public SceneNode CreateChildSceneNode()
        {
            SceneNode node = new SceneNode();
            node.parentSceneNode = this;
            this.addChildSceneNode(node);
            return node;
        }

        /// <summary>
        /// Create a child scene node and registers it in the SceneManager nodelist
        /// </summary>
        public SceneNode CreateChildSceneNode(string nodeName)
        {
            SceneNode node = CreateChildSceneNode();
            Engine.Instance.SceneManager.RegisterNode(node, nodeName);
            return node;
        }

        /// <summary>
        /// Allow acces to ChildSceneNode from derived classes
        /// </summary>
        internal void addChildSceneNode(SceneNode node)
        {
            this.childSceneNodes.Add(node);
        }

        /// <summary>
        /// Allow acces to ChildSceneNode from derived classes
        /// </summary>
        internal void removeChildSceneNode(SceneNode node)
        {
            this.childSceneNodes.Remove(node);
        }

        #endregion

        /// <summary>
        /// Associate a renderable object to this node
        /// </summary>
        public void Attach(Entity entity)
        {
            //if other entity was attached, detach
            if (attachedEntity != null)
            {
                attachedEntity.setParentSceneNode(null);
            }

            //attach the entity
            entity.setParentSceneNode(this);
            attachedEntity = entity;
        }

        /*
        #region "Content Loading"

        /// <summary>
        /// If the entity is attached to a ILoadable entity, load its contents.
        /// The load the contents of all child scenenodes
        /// </summary>
        internal void LoadGraphicsContent(GraphicsDevice graphicsDevice, ContentManager contentManager)
        {
            //load the contents of the attached object if any
            if (attachedEntity is ILoadable)
                (attachedEntity as ILoadable).LoadGraphicsContent(graphicsDevice, contentManager);

            //notify that an asset was loaded
            if (OnLoadTick != null) OnLoadTick();

            //load the contents on the child nodes
            foreach (SceneNode node in this.childSceneNodes)
            {
                //load the child node
                node.LoadGraphicsContent(graphicsDevice, contentManager);

                //notify that an asset was loaded
                if (OnLoadTick != null) OnLoadTick();
            }
        }

        #endregion
        */

        #region "Rendering"

        private bool isBillboard = false;

        /// <summary>
        /// Allow controlling the creation of the world matrix
        /// to align the rotation against the camera position
        /// </summary>
        public bool IsBillBoard
        {
            get { return this.isBillboard; }
            set { this.isBillboard = value; }
        }

        /// <summary>
        /// Render all children
        /// </summary>
        public virtual void Render(GraphicsDevice device)
        {
            if (this.visible)
            {
                //set the worldmatrix
                Engine.Instance.EffectEnvironment.world = WorldMatrix;

                //billboards require cullmode = none
                if (this.isBillboard)
                {
                    device.RenderState.CullMode = CullMode.None;
                }

                //render the entity
                if (this.attachedEntity is IRenderable)
                    (this.attachedEntity as IRenderable).Render(ref WorldMatrix, device);

                //render all children
                foreach (SceneNode node in this.childSceneNodes)
                    node.Render(device);
            }
        }

        /// <summary>
        /// Update the scene node matrixes
        /// </summary>
        public virtual void Update(ref Matrix parentWorldMatrix)
        {
            //if local matrix update needed, recalculate the matrix
            computeLocalMatrix();

            //if we are a billboard, transform the matrix
            if (!this.isBillboard)
            {
                //compute the new world matrix
                Matrix.Multiply(ref this.LocalMatrix, ref parentWorldMatrix, out this.WorldMatrix);
            }
            else
            {
                Matrix.Multiply(ref this.LocalMatrix, ref parentWorldMatrix, out this.WorldMatrix);
                Camera cam = Engine.Instance.CameraManager.ActiveCamera;
                this.WorldMatrix = Matrix.CreateScale(this.Scale) *
                    Matrix.CreateConstrainedBillboard(this.WorldMatrix.Translation, cam.EyePosition, cam.YAxis, null, null);
            }

            //update children
            foreach (SceneNode node in this.childSceneNodes)
                node.Update(ref this.WorldMatrix);
        }

        //temp vars to optimize calculations
        private Matrix _scaleMatrix;
        private Matrix _rotMatrix;
        private Matrix _transMatrix;
        private Matrix _mulTmp;

        /// <summary>
        /// Compute the local matrix and set the flags off
        /// </summary>
        protected void computeLocalMatrix()
        {
            Matrix.CreateFromQuaternion(ref this.Rotation, out _rotMatrix);
            Matrix.CreateTranslation(ref this.Position, out _transMatrix);
            Matrix.CreateScale(ref this.Scale, out _scaleMatrix);

            Matrix.Multiply(ref _rotMatrix, ref _scaleMatrix, out _mulTmp);
            Matrix.Multiply(ref _mulTmp, ref _transMatrix, out this.LocalMatrix);
        }

        #endregion

        /// <summary>
        /// Remove this node and their children from the scene tree
        /// </summary>
        public void Destroy()
        {
            //destroy all childrens
            SceneNode[] destroyNodes = new SceneNode[this.childSceneNodes.Count];
            this.childSceneNodes.CopyTo(destroyNodes, 0);
            foreach (SceneNode node in destroyNodes)
            {
                node.Destroy();
            }

            //remove all chil
            this.childSceneNodes.Clear();

            //remove itself from the parent list
            this.parentSceneNode.childSceneNodes.Remove(this);
        }

        /// <summary>
        /// Changes the paren scene object
        /// </summary>
        internal void SetParent(SceneNode sceneNode)
        {
            this.parentSceneNode.removeChildSceneNode(this);
            this.parentSceneNode = sceneNode;
            this.parentSceneNode.addChildSceneNode(this);
        }

    }

}


